package com.lzx.applib.okhttp.download;

import com.lzx.applib.utils.LogUtil;
import com.lzx.applib.utils.StreamUtil;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.SocketException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * Created by Lzx on 16-12-2.
 */

public class DownloadTask {

    private static final String TAG = "DownloadTask";

    private final DownloadInfo downloadInfo;
    private final OkHttpClient client;
    private final ProgressListener listener;
    private Call call;

    public DownloadTask(OkHttpClient client, DownloadInfo info, ProgressListener listener) {
        this.client = client;
        this.downloadInfo = info;
        this.listener = listener;
    }

    public void start() {
        if (call != null) return;

        try {
            final Request request = new Request.Builder()
                    .url(downloadInfo.remotePath).header("RANGE", "bytes=" + downloadInfo.doneBytes + "-")
                    .build();
            call = client.newCall(request);
            call.enqueue(new Callback() {

                @Override
                public void onFailure(Call _call, IOException e) {
                    LogUtil.d(TAG, "onFailure", downloadInfo);
                    updateState(DownloadInfo.ERROR);
                    call = null;
                }

                @Override
                public void onResponse(Call _call, Response response) throws IOException {
                    LogUtil.d(TAG, "onResponse", downloadInfo);
                    if (downloadInfo.allBytes <= 0) downloadInfo.allBytes = response.body().contentLength();

                    updateState(DownloadInfo.LOADING);
                    save(response);
                    call = null;
                }
            });
        } catch (Exception e) {
            updateState(DownloadInfo.ERROR);
        }
    }

    public void stop() {
        if (call != null) {
            call.cancel();
        }
    }

    void save(Response response) {
        ResponseBody body = response.body();
        InputStream in = body.byteStream();
        RandomAccessFile file = null;
        FileChannel fileChannel = null;

        try {
            file = new RandomAccessFile(downloadInfo.localPath, "rwd");
            fileChannel = file.getChannel();
            MappedByteBuffer mapBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, downloadInfo.doneBytes, body.contentLength());
            byte[] buffer = new byte[1024 * 8];
            int length = 0;
            while ((length = in.read(buffer)) != -1) {
                mapBuffer.put(buffer, 0, length);
                downloadInfo.doneBytes += length;
                updateProgress();
            }
            updateState(DownloadInfo.FINISH);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            updateState(DownloadInfo.ERROR);
        } catch (SocketException e) {
            e.printStackTrace();
            if (call.isCanceled()) {
                updateState(DownloadInfo.PAUSE);
            } else {
                updateState(DownloadInfo.ERROR);
            }
        }catch (IOException e) {
            e.printStackTrace();
            updateState(DownloadInfo.ERROR);
        } finally {
            StreamUtil.closeSafely(in, fileChannel, file);
        }
    }

    DownloadInfo getDownloadInfo() {
        return downloadInfo;
    }

    private void updateProgress() throws SocketException {
        DownloadInfoTable.getInstance().updateProgress(downloadInfo);
        listener.update(downloadInfo.allBytes, downloadInfo.doneBytes);

        if (downloadInfo.allBytes < downloadInfo.doneBytes) {
            throw new SocketException();
        }
    }

    private void updateState(@DownloadInfo.TaskState int state) {
        downloadInfo.state = state;
        DownloadInfoTable.getInstance().updateState(downloadInfo);
        listener.updateState(state);
    }
}
